package me.zhengjie.modules.power.utilVideo;

import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.power.domain.Video;
import me.zhengjie.modules.power.utilVideo.hik.HCNetSDK;

/**
 * 摄像头控制
 */
@Slf4j
public class DvrVideoControl {

    public DvrVideoControl(Video camera) {
        this.camera = camera;
    }

    private int userID = -1;
    private int previewHandle = -1;
    private final Video camera;
    private RealCallBack realCallBack = new RealCallBack();

    /**
     * 清理摄像头
     */
    public void clear(){
        log.error("清理摄像头，{},{}",userID,previewHandle);
        this.stopPreview();
        this.logout();
    }

    /**
     * 摄像头登录
     */
    public int login() {
        if(userID <= -1) {
            HCNetSDK.NET_DVR_DEVICEINFO_V30 netDvrDeviceinfoV30 = new HCNetSDK.NET_DVR_DEVICEINFO_V30();
            userID = DvrVideoManage.getHCNetSDK().NET_DVR_Login_V30(camera.getDeviceIp(), (short) Integer.parseInt(camera.getPort()), camera.getLoginUser(), camera.getPassword(), netDvrDeviceinfoV30);
            if (userID <= -1) {
                int error = DvrVideoManage.getHCNetSDK().NET_DVR_GetLastError();
                log.info("摄像头{}注册失败,错误码：{}", camera.getCameraName(), error);
            }
        }
        return userID;
    }

    /**
     * 摄像头登出
     */
    public boolean logout() {
        // 摄像头登出
        if(userID > -1){
            return DvrVideoManage.getHCNetSDK().NET_DVR_Logout_V30(userID);
        }
        return false;
    }

    /**
     * 摄像头预览推流
     */
    public int startPreview() {
        if(userID <= -1){
            this.login();
        }
        if(previewHandle <= -1){
            HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
            strClientInfo.read();
            // byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值
            //通道号
            strClientInfo.lChannel = 1;
            // 0-主码流，1-子码流，2-三码流，3-虚拟码流，以此类推
            strClientInfo.dwStreamType = 0;
            // 连接方式：0- TCP方式，1- UDP方式，2- 多播方式，3- RTP方式，4- RTP/RTSP，5- RTP/HTTP，6- HRUDP（可靠传输） ，7- RTSP/HTTPS，8- NPQ
            strClientInfo.dwLinkMode = 0;
            //需要 SDK 解码时句柄设为有效值，仅取流不解码时可设为空
            strClientInfo.hPlayWnd = null;
            // 0-非阻塞取流，1-阻塞取流
            strClientInfo.bBlocked = 1;
            strClientInfo.write();
            // 预览句柄
            if (realCallBack == null) {
                realCallBack = new RealCallBack();
            }
            previewHandle = DvrVideoManage.getHCNetSDK().NET_DVR_RealPlay_V40(userID, strClientInfo, realCallBack, null);
            if (previewHandle <= -1) {
                int error = DvrVideoManage.getHCNetSDK().NET_DVR_GetLastError();
                log.info("摄像头{}预览失败,错误码：{}", camera.getCameraName(), error);
            }
        }
        return previewHandle;
    }

    /**
     * 摄像头停止预览
     */
    public boolean stopPreview() {
        // 摄像头停止预览
        if(previewHandle > -1) {
            return DvrVideoManage.getHCNetSDK().NET_DVR_StopRealPlay(previewHandle);
        }
        return false;
    }

    /**
     * 预览回调处理
     */
    private class RealCallBack implements HCNetSDK.FRealDataCallBack_V30 {
        // 视频流数据
        byte[] allEsBytes = null;

        @Override
        public void invoke(int lRealHandle, int dwDataType, com.sun.jna.ptr.ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
            switch (dwDataType) {
                case HCNetSDK.NET_DVR_SYSHEAD:      // 系统头数据
                    break;
                case HCNetSDK.NET_DVR_STREAMDATA:   // 流数据（包括复合流或音视频分开的视频流数据）
                    if (dwBufSize > 0) {
                        byte[] outputData = pBuffer.getPointer().getByteArray(0, dwBufSize);
                        if (outputData.length > 0) {
                            // RTP包开头
                            if ((outputData[0] & 0xff) == 0x00
                                    && (outputData[1] & 0xff) == 0x00
                                    && (outputData[2] & 0xff) == 0x01
                                    && (outputData[3] & 0xff) == 0xBA) {
                                // 一个完整的帧解析完成后将解析的数据放入BlockingQueue,websocket获取后发生给前端
                                if (allEsBytes != null && allEsBytes.length > 0) {
                                    VideoStreamWebSocket.sendInfo(camera.getId(), allEsBytes);
                                    log.info("摄像头[{}]:推送视频流:{}", camera.getCameraName(), allEsBytes);
                                }
                                allEsBytes = null;
                            }

                            // 是00 00 01 eo开头的就是视频的pes包
                            if ((outputData[0] & 0xff) == 0x00
                                    && (outputData[1] & 0xff) == 0x00
                                    && (outputData[2] & 0xff) == 0x01
                                    && (outputData[3] & 0xff) == 0xE0) {
                                // 去掉包头后的起始位置
                                int from = 9 + outputData[8] & 0xff;
                                int len = outputData.length - 9 - (outputData[8] & 0xff);
                                // 获取es裸流
                                byte[] esBytes = new byte[len];
                                System.arraycopy(outputData, from, esBytes, 0, len);

                                if (allEsBytes == null) {
                                    allEsBytes = esBytes;
                                } else {
                                    byte[] newEsBytes = new byte[allEsBytes.length + esBytes.length];
                                    System.arraycopy(allEsBytes, 0, newEsBytes, 0, allEsBytes.length);
                                    System.arraycopy(esBytes, 0, newEsBytes, allEsBytes.length, esBytes.length);
                                    allEsBytes = newEsBytes;
                                }
                            }
                        }
                    }
                    break;
                case HCNetSDK.NET_DVR_AUDIOSTREAMDATA:      // 音频数据
                    break;
                default:
                    break;
            }
        }
    }
}
